#include "qelib.h"
#include "sdtrace.h"
#include <time.h>
#include <getopt.h>



#define DEFAULT_FILENAME            "sdtrace.bin"
#define DEFAULT_DATA_NUM            10000
#define DEFAULT_PERIOD              60
#define DEFAULT_EPOCHS              105
#define DEFAULT_FREQ                1000

#define TEST_LOGNAME                "test"
#define test_debug(...)             qelog_debug(TEST_LOGNAME,   __VA_ARGS__)
#define test_info(...)              qelog_info(TEST_LOGNAME,    __VA_ARGS__)
#define test_notice(...)            qelog_notice(TEST_LOGNAME,  __VA_ARGS__)
#define test_warning(...)           qelog_warning(TEST_LOGNAME, __VA_ARGS__)
#define test_error(...)             qelog_error(TEST_LOGNAME,   __VA_ARGS__)



typedef struct qe_aligned(1)
{
    qe_u32 sec;
    qe_u32 usec;
    qe_u32 data0;
    qe_u32 data1;
    qe_u32 data2;
    qe_u32 data3;
} record_data;

typedef struct 
{
    qe_u16 x;
    qe_u16 y;
} record_target;

typedef struct qe_aligned(1)
{
    qe_u32 frame;
    record_target targets[4];
} record_data2;


static int random_int(int min, int max)
{
    static qe_bool init = qe_false;

    if (init == qe_false) {
        time_t now;
        srand(time(&now));
        init = qe_true;
    }

    return rand() % max + min;
}

static void record_data_random(record_data2 *rd)
{
    qe_timeval tv;
    qe_timeval_get(&tv);

    rd->frame        = rd->frame + 1;
    rd->targets[0].x = random_int(0, 10);
    rd->targets[0].y = random_int(10, 20);
    rd->targets[1].x = random_int(0, 100);
    rd->targets[1].y = random_int(100, 200);
    rd->targets[2].x = random_int(0, 1000);
    rd->targets[2].y = random_int(1000, 2000);
    rd->targets[3].x = random_int(0, 10000);
    rd->targets[3].y = random_int(10000, 20000);
    // rd->targets[0].x = 1;
    // rd->targets[0].y = 2;
    // rd->targets[1].x = 3;
    // rd->targets[1].y = 4;
    // rd->targets[2].x = 5;
    // rd->targets[2].y = 6;
    // rd->targets[3].x = 7;
    // rd->targets[3].y = 8;
}

static void usage(void)
{
    printf("\n");
    printf("sdtrace-test <cmd> <opt>\n");
    printf("  -h,?,--help                print help information\n");
    printf("  -o,--output                save filename, default:%s\n", DEFAULT_FILENAME);
    printf("  -n,--num <val>             set number of datas, default:%d\n", DEFAULT_DATA_NUM);
    printf("  -p,--period <val>          set record period(s), default:%d\n", DEFAULT_PERIOD);
    printf("  -e,--epochs <val>          set testing epochs, default:%d\n", DEFAULT_EPOCHS);
    printf("  -f,--freq <val>            set record freq(ms), default:%d\n", DEFAULT_FREQ);
    printf("\n");
}

typedef struct
{
    char *name;
    FILE *fp;
    qe_gbuf *dummy;
} trace_user;

// typedef struct
// {
//     qe_dev *dev;
//     qe_u32 base;
//     qe_U32 offset;
// } sdtrace_flash_stream_user;

// static qe_ret flash_open(sdtrace_stream *stream, qe_u8 mode, qe_uint size)
// {
//     sdtrace_flash_stream_user *user = (sdtrace_flash_stream_user *)stream->user;

//     qe_dev_open(user-dev, 0x0);
//     user->offset = 0;

//     return qe_ok;
// }

// static qe_int flash_read(sdtrace_stream *stream, qe_u8 *buf, qe_uint size)
// {
//     qe_u32 addr;
//     sdtrace_flash_stream_user *user = (sdtrace_flash_stream_user *)stream->user;
//     addr = user->base + user->offset;
//     qe_dev_read(user->dev, user->addr, buf, size);
//     user->offset += size;
//     return size;
// }

// static qe_int flash_write(sdtrace_stream *stream, qe_u8 *buf, qe_uint size)
// {
//     qe_u32 addr;
//     sdtrace_flash_stream_user *user = (sdtrace_flash_stream_user *)stream->user;
//     addr = user->base + user->offset;
//     qe_dev_write(user->dev, user->addr, buf, size);
//     user->offset += size;
//     return size;
// }

// static sdtrace_file_stream_user file_stream_user = {
//     .name = DEFAULT_FILENAME,
//     .fp = QE_NULL,
// };

// static qe_ret file_open(sdtrace_stream *stream, qe_u8 mode, qe_uint size)
// {
//     sdtrace_file_stream_user *user = (sdtrace_file_stream_user *)stream->user;

//     if (user->fp) {
//         test_warning("already open");
//         return qe_ok;
//     }

//     if (mode == SDTRC_STREAM_RB) {
//         test_debug("read open %s", user->name);
//         user->fp = fopen(user->name, "rb");
//     } else {
//         test_debug("write open %s", user->name);
//         user->fp = fopen(user->name, "wb");
//     }
//     if (!user->fp) {
//         test_error("open file %s error", user->name);
//         return qe_err_common;
//     }

//     return qe_ok;
// }

// static qe_ret file_close(sdtrace_stream *stream)
// {
//     sdtrace_file_stream_user *user = (sdtrace_file_stream_user *)stream->user;

//     if (!user->fp) {
//         test_error("no fp");
//         return qe_err_param;
//     }

//     fclose(user->fp);
//     user->fp = QE_NULL;

//     return qe_ok;
// }

// static qe_int file_read(sdtrace_stream *stream, qe_u8 *buf, qe_uint size)
// {
//     int n;
//     sdtrace_file_stream_user *user = (sdtrace_file_stream_user *)stream->user;
//     n = fread(buf, size, 1, user->fp);
//     if (n < 0) {
//         test_error("file read error");
//         return -1;
//     }
//     return size;
// }

// static qe_int file_write(sdtrace_stream *stream, qe_u8 *buf, qe_uint size)
// {
//     int n;
//     sdtrace_file_stream_user *user = (sdtrace_file_stream_user *)stream->user;
//     n = fwrite(buf, size, 1, user->fp);
//     if (n < 0) {
//         test_error("file write error");
//         return -1;
//     }
//     return size;
// }

// static sdtrace_stream file_stream = 
// {
//     .open  = file_open,
//     .read  = file_read,
//     .write = file_write,
//     .close = file_close,
//     .user  = &file_stream_user,
// };

// static sdtrace_stream flash_stream = {
//     .open  = flash_open,
//     .read  = flash_read,
//     .write = flash_write,
//     .close = flash_close,
//     .user  = flash_stream_user,
// };

static qe_ret null_init(qe_sdtrace_blockio *bio)
{
    return qe_ok;
}

static qe_ret null_erase(qe_sdtrace_blockio *bio, qe_u32 block)
{
    test_debug("erase block %d", block);
    return qe_ok;
}

static qe_ret null_write(qe_sdtrace_blockio *bio, qe_u32 chunk, 
    qe_const_ptr data, qe_uint len)
{
    return qe_ok;
}

static qe_ret null_read(qe_sdtrace_blockio *bio, qe_u32 chunk, 
    qe_ptr data, qe_uint len)
{
    return qe_ok;
}

static qe_bool file_exist(qe_const_str name)
{
    FILE *fp = fopen(name, "r");
    if (fp != QE_NULL) {
        fclose(fp);
        return qe_true;
    }
    return qe_false;
}

static qe_ret file_init(qe_sdtrace_blockio *bio)
{
    qe_int i;
    qe_bool exist;
    trace_user *user = (trace_user *)bio->user;

    test_debug("file init");

    exist = file_exist(user->name);

    user->dummy = qe_gbuf_new(bio->block_size);
    qe_memset(qe_gbuf_pos(user->dummy), 0xFF, qe_gbuf_size(user->dummy));

    if (!exist) {
        user->fp = fopen(user->name, "w+b");
        if (!user->fp) {
            test_error("open file %s error", user->name);
            return qe_err_common;
        }
        test_debug("write dummy");
        for (i=0; i<bio->num_blocks; i++) {
            fwrite(qe_gbuf_pos(user->dummy), 1, 
                qe_gbuf_size(user->dummy), user->fp);
        }
    } else {
        user->fp = fopen(user->name, "rb+");
        if (!user->fp) {
            test_error("open file %s error", user->name);
            return qe_err_common;
        }
    }
    
    return qe_ok;
}

static qe_ret file_erase(qe_sdtrace_blockio *bio, qe_u32 block)
{
    qe_u32 offset;
    qe_u32 block_size;
    trace_user *user = (trace_user *)bio->user;
    test_debug("erase block %d", block);
    offset = block * bio->block_size;
    fseek(user->fp, offset, SEEK_SET);
    fwrite(qe_gbuf_pos(user->dummy), 1, 
        qe_gbuf_size(user->dummy), user->fp);
    return qe_ok;
}

static qe_ret file_write(qe_sdtrace_blockio *bio, qe_u32 chunk, 
    qe_const_ptr data, qe_uint len)
{
    qe_u32 offset;
    //test_debug("write chunk %d", chunk);
    trace_user *user = (trace_user *)bio->user;
    offset = chunk * bio->chunk_size;
    fseek(user->fp, offset, SEEK_SET);
    fwrite(data, 1, len, user->fp);
    return qe_ok;
}

static qe_ret file_read(qe_sdtrace_blockio *bio, qe_u32 chunk, 
    qe_ptr data, qe_uint len)
{
    qe_u32 offset;
    test_debug("read chunk %d %d", chunk, len);
    trace_user *user = (trace_user *)bio->user;
    offset = chunk * bio->chunk_size;
    fseek(user->fp, offset, SEEK_SET);
    fread(data, 1, len, user->fp);
    return qe_ok;
}

static trace_user file_blockio_user = {
    .fp = QE_NULL,
    .name = DEFAULT_FILENAME,
};

static qe_sdtrace_blockio file_blockio = {
    .init        = file_init,
    .block_erase = file_erase,
    .chunk_read  = file_read,
    .chunk_write = file_write,
    .config = {
        .chunk_size = 512,
        .chunks_per_block = 8,
        .num_cache_blocks = 64,
        .start_block = 0,
        .end_block = 8192,
    },
    .user = &file_blockio_user,
};

static qe_sdtrace_blockio null_blockio = {
    .init        = null_init,
    .block_erase = null_erase,
    .chunk_read  = null_read,
    .chunk_write = null_write,
    .config = {
        .chunk_size = 512,
        .chunks_per_block = 8,
        .num_cache_blocks = 256,
        .start_block = 0,
        .end_block = 14000,
    },
    .user = QE_NULL,
};

int main(int argc, char *argv[])
{
    int opt;
    int size;
    FILE *fp;
    qe_ret ret;
    qe_uint epochs = DEFAULT_EPOCHS;
    qe_uint freq   = DEFAULT_FREQ;
    record_data2 data;
    qe_sdtrace *trace;

    qelog_init(QELOG_DEBUG, QELOG_CL|QELOG_DM|QELOG_LV|QELOG_DATE);
    qelog_domain_set_level(QELOG_DOMAIN_SDTRACE, QELOG_INFO);

    static const struct option long_opts[] = {
        {"output",   required_argument, QE_NULL, 'o'},
        {"epochs",   required_argument, QE_NULL, 'e'},
        {"freq",     required_argument, QE_NULL, 'f'},
        {"num",      required_argument, QE_NULL, 'n'},
        {"period",   required_argument, QE_NULL, 'p'},
        {"help",     no_argument,       QE_NULL, 'h'},
    };

    while ((opt = getopt_long(argc, argv, "o:e:f:n:p:?h-", long_opts, NULL)) != -1) {
        
        switch (opt) {

        case 'o':
            file_blockio_user.name = optarg;
            break;

        case 'e':
            epochs = atoi(optarg);
            break;

        case 'f':
            freq = atoi(optarg);
            break;

        case 'h':
        case '?':
        default:
            usage();
            exit(EXIT_SUCCESS);
        }
    }

    trace = qe_sdtrace_new(sizeof(record_data2), &file_blockio);
    if (!trace) {
        test_error("trace create error");
        return -1;
    }
    test_debug("sdtrace new success");

    qe_sdtrace_load(trace);
    
    while (epochs--) {
        record_data_random(&data);
        qe_sdtrace_record_datas(trace, &data, 1);
        qe_usleep(freq);
    }

    qe_sdtrace_destroy(trace);

    fclose(file_blockio_user.fp);

    return 0;
}